Let’s start examining the steps required to build an ASP.NET AJAX service using an .asmx endpoint. The service is part of a server layer that your pages interact with using JavaScript.
Web Services as Application-Specific Services
ASP.NET doesn’t just let
you call into any SOAP-based Web services from JavaScript. When you
take advantage of AJAX extensions for ASP.NET, you use JavaScript to
place calls into some server code within the boundaries your own
application and domain. In some way, the application server code must be
exposed to the client. The way in which this happens depends
on the capabilities of the platform. In ASP.NET 2.0 with AJAX
Extensions installed, you can rely only on ASP.NET XML Web services
local to the application (as modified to return JSON data). In ASP.NET
3.5, you can also employ WCF services. External Web services—those being
services outside your application’s domain—cannot be invoked directly
from the client for security reasons, neither in ASP.NET 2.0 nor ASP.NET
3.5. This is by design.
By default, ASP.NET Web
services work by sending and receiving SOAP packets instead of JSON
packets and expose their contract using a Web Services Description
Language (WSDL) document. What about ASP.NET XML Web services working in
the context of an AJAX application?
The web.config file of an ASP.NET AJAX application can modify the HTTP handler that receives .asmx
requests and redirect these calls to an HTTP handler that understands
JSON streams. This means that an ASP.NET XML Web service can be a dual
service and can be able to accept and serve both SOAP and JSON requests.
Acting at the configuration level, though, you can disable any SOAP
support and hide any WSDL file for public discovery of the service
functionalities.
And since I’ll be
referring to JSON-enabled ASP.NET Web services, from this point forward
I’ll drop the “XML” since we won’t be working with SOAP and XML when
invoking ASP.NET Web services. ASP.NET Web services for AJAX
applications do not use any SOAP messages.
Defining the Remote API
A contract is used to
specify what the server-side endpoints expose to callers. If you plan to
implement the service as an ASP.NET Web service, an explicit contract
is not strictly required. A contract, instead, is mandatory if you opt
for a WCF service in ASP.NET 3.5. All in all, designing the public API
as an interface produces cleaner code, which is never a bad thing. When
you’re done with the interface of the server API, you proceed with the
creation of a class that implements the interface. Finally, you publish
the remote API and let the ASP.NET AJAX runtime manage calls from the
client.
For ASP.NET Web services,
you define the contract through a plain interface that groups methods
and properties for the server API. Here’s an example of a simple service
that returns the current time on the server:
using System;
public interface ITimeService
{
DateTime GetTime();
string GetTimeFormat(string format);
}
The contract exposes two methods: GetTime and GetTimeFormat. These methods form the server API that can be called from within the client.
Warning
You
are on your own when implementing a given interface in an ASP.NET Web
service. There’s no automatic runtime check to enforce the requirement
that exactly those methods are exposed by the server API. |
Implementing the Contracted Interface
After you have defined
the server API you want to invoke from the client, you implement it in a
class and then bind the class to a publicly addressable endpoint.
An ASP.NET Web service is usually implemented through a .NET class that derives from the WebService base class. Here’s an example:
using System.Web.Services;
public class TimeService : WebService, ITimeService
{
...
}
To direct the Web
service to support a given interface, you simply add the interface type
to the declaration statement and implement corresponding methods in the
body of the class.
Note that deriving from the WebService base class is optional and serves primarily to gain the service direct access to common ASP.NET objects, such as Application and Session.
If you don’t need direct access to the intrinsic ASP.NET objects, you
can still create an ASP.NET Web service without deriving from the WebService class. In this case, you can still use ASP.NET intrinsics through the HttpContext object.
Publishing the Contract
Now that we have
defined the formal contract and implementation of the server API of an
ASP.NET AJAX application, one more step is left—publishing the contract.
How do you do that?
Publishing the
contract means making the server API visible to the JavaScript client
page and, subsequently, enabling the JavaScript client page to place
calls to the server API. From the client page, you can invoke any object
that is visible to the JavaScript engine. In turn, the JavaScript
engine sees any class that is linked to the page. In the end, publishing
a given server contract means generating a JavaScript proxy class that
the script embedded in the page can command.
When the server API is
implemented through a Web service, you register the Web service with the
script manager control of the ASP.NET AJAX page. In addition, you add a
special HTTP handler for .asmx requests in the application’s web.config file.
Let’s expand upon the topic of AJAX Web services development by exploring a few examples.
Remote Calls via Web Services
Web
services provide a natural environment for hosting server-side code
that needs to be called in response to a client action such as clicking a
button. The set of Web methods in the service refers to pieces of code
specific to the application.
Creating an AJAX Web Service
A Web service made to
measure for an ASP.NET AJAX application is similar to any other ASP.NET
Web service you might write for whatever purposes. Two peripheral
aspects, though, delineate a clear difference between ASP.NET AJAX Web
services and traditional ASP.NET XML Web services.
First and foremost,
when working with ASP.NET AJAX Web services, you design the contract of
an ASP.NET AJAX Web service to fit the needs of a particular application
rather than to configure the behavior of a public service. The target
application is also the host of the Web service. Second, you must use a
new attribute to decorate the class of the Web service that is not
allowed on regular ASP.NET XML Web services.
The effect of this is, in
the end, that an ASP.NET AJAX Web service might have a double public
interface: the JSON-based interface consumed by the hosting ASP.NET AJAX
application, and the classic SOAP-based interface exposed to any
clients, from any platforms, that can reach the service URL.
The ScriptService Attribute
To create an ASP.NET AJAX Web service, you first set up a standard ASP.NET Web service project. Next, you import the System.Web.Script.Services namespace:
using System.Web.Script.Services;
The attribute that establishes a key difference between ASP.NET XML Web services and ASP.NET AJAX Web services is the ScriptService attribute. You apply the attribute to the service class declaration, as shown here:
namespace Core35.WebServices
{
[WebService(Namespace = "http://core35.book/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class TimeService : System.Web.Services.WebService, ITimeService
{
...
}
}
The ScriptService
attribute indicates that the service is designed to accept calls from
JavaScript-based client proxies. If the Web service lacks the attribute,
an exception is thrown on the server when you attempt to place calls
from your AJAX-enabled client. Figure 1 shows the message that is returned when an AJAX page links to a service not flagged with the attribute.
More precisely, the page
shown in the figure is never displayed to any end users. The markup
comes with an HTTP 500 error code when access is attempted to a
nonscriptable Web service from JavaScript. To get the screen shot in Figure 20-2, I intercepted the HTTP 500 response, saved the body to a local file, and displayed the HTML file in a browser.
The internal ASP.NET machinery refuses to process any calls directed at ASP.NET AJAX Web services that lack the ScriptService attribute.
Important
You
should avoid exposing sensitive pieces of the middle tier to the public
without a well-configured security barrier. It is recommended, then,
that you add to the Web service only methods that form a sort of user
interface–level business logic, where no critical task is accomplished.
In addition, you should consider adding a validation layer in the body
of Web methods and perhaps using network-level tools to monitor calling
IP addresses and, if needed, block some of them. Finally, you can
consider SSL/TLS even. That won’t preclude unauthorized use, but it
helps with snooping when the use is authorized. |
Blocking SOAP Clients
Once
created, an AJAX Web service is published as an ASMX resource. By
default, it’s a public URL and can be consumed by AJAX clients, as well
as discovered and consumed by SOAP clients and tools. But you can opt to
disable SOAP clients and tools altogether. Just enter the following
configuration settings to the web.config of the ASP.NET application that hosts the service:
<webServices>
<protocols>
<clear />
</protocols>
</webServices>
This simple setting
disables any protocols defined for ASP.NET Web services (in particular,
SOAP) and lets the service reply only to JSON requests. Note that with
these settings on, you can no longer call the Web service through the
browser’s address bar for a quick test. Likewise, you can’t ask for the
WSDL by adding the ?wsdl suffix to the URL.
Defining Methods for a Web Service
Public methods of the Web service class decorated with the WebMethod
attribute can be invoked from the client page. Any method is invoked
using the HTTP POST verb and return its values as a JSON object. You can
change these default settings on a per-method basis by using an
optional attribute—ScriptMethod.
The ScriptMethod attribute features three properties, as described in Table 1.
Table 1. Properties of the ScriptMethod Attribute
Property | Description |
---|
ResponseFormat | Specifies
whether the response will be serialized as JSON or as XML. The default
is JSON, but the XML format can come in handy when the return value of
the method is an XmlDocumentXMLHttpRequest
has the native ability to expose the response as an XML DOM, using JSON
you save unnecessary serialization and deserialization overhead. object. In this case, because |
UseHttpGet | Indicates whether an HTTP GET verb should be used to invoke the Web service method. The default is false,
meaning that the POST verb is used. The GET verb poses some security
issues, especially when sensitive data is being transmitted. All the
data, in fact, is stored in the URL and is visible to everybody. |
XmlSerializeString | Indicates whether all return types, including strings, are serialized as XML. The default is false. The value of the property is ignored when the response format is JSON. |
Because of the repercussions it might have on security and performance, the ScriptMethod attribute should be used very carefully. The following code uses the attribute without specifying nondefault settings:
[WebMethod]
[ScriptMethod]
public DateTime GetTime()
{
...
}
The WebMethod attribute is required; the ScriptMethod attribute is optional. You should use the ScriptMethod attribute only when you need to change some of the default settings. In general, you should have very good reasons to use the ScriptMethod attribute.
Registering AJAX Web Services
To place calls to an ASP.NET Web service from the client, all that you need is the XMLHttpRequest
object, the URL of the target Web service, and the ability to manage
JSON streams. For convenience, all this functionality is wrapped up in a
JavaScript proxy class that mirrors the remote API. The JavaScript
proxy is automatically generated by the ASP.NET AJAX framework and
injected into the client page.
To trigger the
built-in engine that generates any required JavaScript proxy and helper
classes, you register the AJAX Web service with the script manager
control of each page where the Web service is required. You can achieve
this both declaratively and programmatically. Here’s how to do it
declaratively from page markup:
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/WebServices/TimeService.asmx" />
</Services>
</asp:ScriptManager>
You add a ServiceReference tag for each Web service bound to the page and set the Path attribute to a relative URL for the .asmx resource. Each service reference automatically produces an extra <script>
block in the client page. The URL of the script points to a system HTTP
handler that, under the hood, invokes the following URL:
~/WebServices/TimeService.asmx/js
The /js
suffix appended to the Web service URL instructs the ASP.NET AJAX
runtime to generate the JavaScript proxy class for the specified Web
service. If the page runs in debug mode, the suffix changes to /jsdebug and a debug version of the proxy class is emitted.
By default, the JavaScript proxy is linked to the page via a <script> tag and thus requires a separate download. You can also merge any needed script to the current page by setting the InlineScript attribute of the ServiceReference object to true. The default value of false is helpful
if browser caching is enabled and multiple Web pages use the same
service reference. In this case, therefore, only one additional request
is executed, regardless of how many pages need the proxy class. A value
of true for the InlineScript
property reduces the number of network requests at the cost of
consuming a bit more bandwidth. This option is preferable when there are
many service references in the page and most pages do not link to the
same services.
To register AJAX Web services programmatically, you add the following code, preferably in the Page_Load event of the page’s code-behind class:
ServiceReference service = new ServiceReference();
service.Path = "~/WebServices/TimeService.asmx";
ScriptManager1.Services.Add(service);
Whatever route you
take, to invoke the Web service you need to place a call to the proxy
class using JavaScript. The proxy class has the same name as the Web
service class and the same set of methods. We’ll return to this topic in
a moment.
Configuring ASP.NET Applications to Host AJAX Web Services
To enable Web service calls from within ASP.NET AJAX applications, you need to add the following script to the application’s web.config file and register a special HTTP handler for .asmx requests:
<httpHandlers>
<remove verb="*" path="*.asmx" />
<add verb="*" path="*.asmx"
type="System.Web.Script.Services.ScriptHandlerFactory" />
...
</httpHandlers>
This setting is included in the default web.config file that Microsoft Visual Studio 2008 creates for you when you create an AJAX-enabled Web project.
A handler factory
determines which HTTP handler is in charge of serving a given set of
requests. The specialized ASP.NET AJAX Web service handler factory for .asmx
requests distinguishes JSON calls made by script code from ordinary Web
service calls coming from SOAP-based clients, including ASP.NET and
Windows Forms applications. JSON-based requests are served by a
different HTTP handler, whereas regular SOAP calls take the usual route
in the ASP.NET pipeline.